package boardgamer.yavalath.client.animation;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import boardgamer.common.client.animation.Animation;
import boardgamer.common.client.layout.BitmapLayout;
import boardgamer.common.client.layout.HasLayout;
import boardgamer.common.client.layout.InvisibleRect;
import boardgamer.common.client.layout.LayoutGroup;
import boardgamer.common.client.layout.TextLayout;
import boardgamer.yavalath.client.resources.ImageHandler;
import boardgamer.yavalath.client.RenderHelper;
import boardgamer.yavalath.common.YavalathPlayerState;

import static boardgamer.yavalath.client.animation.ExpandingHeaderAnimation.FILL_ALPHA;
import static boardgamer.yavalath.client.animation.ExpandingHeaderAnimation.OUTLINE_ALPHA;

/**
 * @author garysoed
 */
public class HeaderAnimation extends Animation implements HasLayout {

  private static final float STROKE_WIDTH_MAX_HEIGHT_RATIO = 0.1f;
  private static final float HEX_TEXT_WIDTH_RATIO = 0.95f;
  private static final int MAX_CHAR_COUNT = 14;

  private static final float TAN30 = (float) (1f / Math.sqrt(3));

  private static final int EXPAND_TIME = 300;
  private static final int UPDATE_PERIOD = 25;

  private enum State {
    HIDDEN,
    APPEARING,
    DISPLAYING,
    CHANGING_FROM,
    CHANGING_TO
  }

  private State state = State.HIDDEN;

  private int timeStep = 0;
  private BitmapLayout renderedText;

  private String textToUse;
  private int colorToUse;

  private final Paint hexOutlinePaint;
  private final Paint hexFillPaint;
  private final Path hex;

  private final Paint textPaint;

  private final LayoutGroup group;
  private final ExpandingHeaderAnimation expandingHeaderAnimation;
  private final RectF flippedBoundingRect;

  private RectF boundingRect;
  private RectF textCurrentRect;
  private RectF textBoundingRect;

  public HeaderAnimation(float maxWidth, float maxHeight) {
    super(UPDATE_PERIOD);

    float maxTextWidth = maxWidth * HEX_TEXT_WIDTH_RATIO;
    this.expandingHeaderAnimation = new ExpandingHeaderAnimation(maxWidth, maxHeight,
        maxTextWidth, MAX_CHAR_COUNT);

    this.hexOutlinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    this.hexOutlinePaint.setStrokeWidth(maxHeight * STROKE_WIDTH_MAX_HEIGHT_RATIO);
    this.hexOutlinePaint.setStyle(Paint.Style.STROKE);

    this.hexFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    this.hexFillPaint.setStyle(Paint.Style.FILL);

    this.hex = new Path();

    RectF textBoundSizeRatio
        = ImageHandler.boundSizeRatio.get(RenderHelper.instance().getHexLetterFont());
    float textSize = maxTextWidth / textBoundSizeRatio.width() / MAX_CHAR_COUNT;
    this.textPaint = RenderHelper.instance().getHexLetterPaint();
    this.textPaint.setTextSize(textSize);

    InvisibleRect textContainer
        = new InvisibleRect(0, 0, maxWidth, textSize * textBoundSizeRatio.height());

    this.group = new LayoutGroup();
    this.group
        .add(new InvisibleRect(0, 0, maxWidth, maxHeight)).atCurrentRect()
        .add(expandingHeaderAnimation).atCurrentRect()
        .add(textContainer).atCurrentRect()
        .horizontalAlign(LayoutGroup.HorizontalAlign.CENTER)
        .verticalAlign(LayoutGroup.VerticalAlign.CENTER);
    this.boundingRect = group.getBoundingRect();

    this.flippedBoundingRect = new RectF();
  }

  public void display(String text, YavalathPlayerState.PlayerColor playerColor) {
    display(text, RenderHelper.instance().getColor(playerColor));
  }
  
  public void display(String text, int color) {
    this.textToUse = text;
    this.colorToUse = color;

    switch (state) {
      case HIDDEN:
        updateTextAndColor();
        expandingHeaderAnimation.display(text, color);
        state = State.APPEARING;
        break;

      case APPEARING:
        updateTextAndColor();
        break;

      case DISPLAYING:
        timeStep = 0;
        state = State.CHANGING_FROM;
        break;

      case CHANGING_FROM:
        timeStep = 0;
        updateTextAndColor();
        break;

      case CHANGING_TO:
        timeStep = 0;
        state = State.CHANGING_FROM;
        break;
    }
  }

  private void updateTextAndColor() {
    TextLayout textToRender = new TextLayout(this.textToUse, textPaint);

    RectF textRect = textToRender.getBoundingRect();
    Bitmap textBitmap = Bitmap.createBitmap(
        Math.round(textRect.width()),
        Math.round(textRect.height()),
        Bitmap.Config.ARGB_8888);
    Canvas textCanvas = new Canvas(textBitmap);
    textToRender.draw(textCanvas);
    renderedText = new BitmapLayout(textBitmap);

    // Center align the text
    new LayoutGroup()
        .add(new InvisibleRect(getBoundingRect())).atCurrentRect()
        .add(renderedText).atCurrentRect()
        .horizontalAlign(LayoutGroup.HorizontalAlign.CENTER)
        .verticalAlign(LayoutGroup.VerticalAlign.CENTER)
        .setBoundingRect(group.getBoundingRect());

    textBoundingRect = renderedText.getBoundingRect();
    textCurrentRect = renderedText.getBoundingRect();

    hexFillPaint.setColor(this.colorToUse);
    hexFillPaint.setAlpha(FILL_ALPHA);   // WTF: setting color resets the alpha
    hexOutlinePaint.setColor(this.colorToUse);
    hexOutlinePaint.setAlpha(OUTLINE_ALPHA);  // WTF: setting color resets the alpha
  }

  @Override
  public void draw(Canvas canvas) {
    switch (state) {
      case HIDDEN:
        break;
      case APPEARING:
        expandingHeaderAnimation.draw(canvas);
        break;
      case DISPLAYING:
      case CHANGING_FROM:
      case CHANGING_TO:
        canvas.drawPath(hex, hexFillPaint);
        canvas.drawPath(hex, hexOutlinePaint);
        renderedText.setBoundingRect(textCurrentRect);
        renderedText.draw(canvas);
        break;
    }
  }

  @Override
  public void setBoundingRect(RectF boundingRect) {
    group.setBoundingRect(boundingRect);
    this.boundingRect = group.getBoundingRect();
  }

  @Override
  public RectF getBoundingRect() {
    return group.getBoundingRect();
  }

  @Override
  public boolean isDead() {
    return false;  // this is never dead
  }

  @Override
  protected void onPeriodic() {
    switch (state) {
      case HIDDEN:
        break;

      case APPEARING:
        expandingHeaderAnimation.update(UPDATE_PERIOD);
        if (expandingHeaderAnimation.isDead()) {
          state = State.DISPLAYING;
          RenderHelper.instance().updateHoriHexPath(
              hex, boundingRect, boundingRect.height() / 2 * TAN30);
        }
        break;

      case CHANGING_FROM:
        timeStep += UPDATE_PERIOD;
        updateFlippedDrawings();

        if (timeStep >= EXPAND_TIME / 2) {
          timeStep = EXPAND_TIME / 2;
          state = State.CHANGING_TO;
          updateTextAndColor();
        }
        break;

      case CHANGING_TO:
        timeStep += UPDATE_PERIOD;
        updateFlippedDrawings();

        if (timeStep >= EXPAND_TIME) {
          timeStep = EXPAND_TIME;
          state = State.DISPLAYING;
        }
        break;
    }
  }

  private void updateFlippedDrawings() {
    float hexHeight = getFlippedHeight(boundingRect.height());
    flippedBoundingRect.set(
        boundingRect.left,
        boundingRect.centerY() - hexHeight / 2,
        boundingRect.right,
        boundingRect.centerY() + hexHeight / 2);
    RenderHelper.instance().updateHoriHexPath(
        hex, flippedBoundingRect, boundingRect.height() / 2 * TAN30);

    float textHeight = getFlippedHeight(textBoundingRect.height());
    textCurrentRect.top = textBoundingRect.centerY() - textHeight / 2;
    textCurrentRect.bottom = textBoundingRect.centerY() + textHeight / 2;
    renderedText.setBoundingRect(textCurrentRect);
  }

  private float getFlippedHeight(float maxHeight) {
    return Math.abs(maxHeight * (1f - 2f / EXPAND_TIME * timeStep));
  }
}
